fix(server): fetch change request heads from each host's ref namespace - #6532
fix(server): fetch change request heads from each host's ref namespace#6532lnieuwenhuis wants to merge 6 commits into
Conversation
Worktree checkout of a GitLab merge request failed for every MR, because fetchPullRequestBranch and fetchPullRequestHeadCommit built GitHub's refs/pull/<n>/head themselves. GitLab publishes the head at refs/merge-requests/<iid>/head, so the fetch exited 128 and both the primary and the fallback attempt failed identically. The driver now takes the ref to fetch and GitManager names it from the host that resolved the change request. Hosts that publish no such ref at all (Bitbucket, and Azure DevOps, which publishes only the merge result) fetch the head branch from the primary remote instead, except for a cross-repository head, where a branch of the same name there is somebody else's work.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — The PR changes existing change-request worktree behavior across several source-control hosts and adds line-level suppressions for a static-analysis diagnostic in new tests. Those suppressions require human review under the applicable approval criteria. You can add or adjust custom eligibility rules. Learn more. |
|
Closing this one — open since Aug 13 with no human review, and the git manager / VCS driver code it touches has moved on main since. Happy to reopen against current main if the per-host ref namespace fix is still wanted. |
|
Reopening — I re-checked against current
|
|
@t3dotgg @juliusmarminge I'd still like your thoughts on this, you can close this PR if it's not a worry for you, but every bug squashed is one less banana peel for the future, right? |
…request-refs # Conflicts: # apps/server/src/git/GitManager.ts
Dismissing prior approval to re-evaluate f1befa3
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 679d16f. Configure here.

What Changed
fetchPullRequestBranchandfetchPullRequestHeadCommitinGitVcsDriverCoreno longer buildGitHub's
refs/pull/<n>/headthemselves. They now take the ref to fetch, andGitManager.preparePullRequestThreadnames it from the host that resolved the change request:github->refs/pull/<n>/head(unchanged)gitlab->refs/merge-requests/<n>/headbitbucket/azure-devops-> no such ref exists, so the head branch is fetched from theprimary remote by name instead. Skipped for a cross-repository head, where a branch of the same
name on the primary remote is somebody else's work; that case now reports why it cannot resolve
the head rather than failing on a fetch that could never have worked.
The mapping is one pure function,
changeRequestHeadRef, next to the other pure source-controlhelpers in
apps/server/src/sourceControl/SourceControlProvider.ts. The host kind comes fromChangeRequest.provideron the change request thatpreparePullRequestThreadhas alreadyresolved, so nothing extra is looked up and the ref can never disagree with the number it was
built from.
Why
Worktree checkout of a GitLab merge request fails for every MR (#6448). GitLab publishes the head
at
refs/merge-requests/<iid>/head, never underrefs/pull, so the fetch exits 128 withfatal: couldn't find remote ref refs/pull/533/head. Both the primary and the fallback attempt inmaterializePullRequestHeadBranchran the same hardcoded refspec, so both failed identically andpreparePullRequestThreadfailed for every MR in worktree mode. Local mode was unaffected becauseit goes through the provider's own
checkoutChangeRequest. Azure DevOps and Bitbucket were brokenthe same way, since neither publishes
refs/pull/<n>/headeither.The head ref is host knowledge, and the git driver has none: it only knows a repository path and a
remote name. The provider registry already resolves the host for the cwd, and the resolved change
request already carries its kind, so the smallest honest fix is to let the caller name the ref and
keep the driver purely git-level.
Alternatives considered and rejected:
changeRequestHeadRefmethod on theSourceControlProviderservice. Four providerimplementations plus two registry wrappers (
unsupportedProvider,bindProviderContext) wouldeach need an entry, and every one would return a constant derived from the kind. The caller
already has the kind; the indirection buys nothing.
reliably identifiable from its URL, which is exactly why the registry has a discovery probe to
refine an unknown remote. It would also duplicate provider detection in the git layer.
headBranchby name (the issue's third suggestion). It fixes same-repositoryMRs but gives up what the published ref is for: closed merge requests, heads whose branch was
deleted or renamed, and fork heads. It is used here only for hosts that publish no ref at all.
Verification
No real self-hosted GitLab remote was exercised — there is none available here. The new tests build
real local git repositories and publish the head under
refs/merge-requests/533/headand nowhereelse, which is the shape that made the original fetch fail.
cd apps/server && pnpm exec vp test run src/git/GitManager.test.ts src/sourceControl/SourceControlProvider.test.tscd apps/server && pnpm exec tsgo --noEmitpnpm exec vp fmt --checkandpnpm exec vp linton the changed filesBoth new
GitManagertests were confirmed to fail against the old ref layout (exit 128 onGitVcsDriver.fetchPullRequestBranch, both attempts) before the fix, and the existing GitHub teststhat publish to
refs/pull/<n>/headstill pass.Closes #6448
Checklist
Note
Medium Risk
Touches PR/MR worktree checkout and git fetch paths across providers; behavior changes for hosts without a published head ref and for cross-repo materialization errors.
Overview
Fixes GitLab (and similar) worktree preparation by stopping hardcoded GitHub
refs/pull/<n>/headfetches. The git layer now takes an explicit head ref from a newchangeRequestHeadRefhelper (GitHub vs GitLab namespaces; null for Bitbucket/Azure DevOps).GitManager.preparePullRequestThreadresolves that ref from the change request’s provider, passes it through head materialization and reused-worktree refresh, and when there is no published head ref fetches the head branch on the primary remote instead—while cross-repository heads without resolvable fork metadata get a clearGitManagerErrorinstead of a doomedrefs/pullfallback.Azure DevOps changes decode fork provenance (
isCrossRepository,headRepositoryNameWithOwner) and qualifyaz repos showwith project for fork clone URLs. Tests cover GitLab MR refs, Bitbucket branch-only heads, and Azure fork parsing.Reviewed by Cursor Bugbot for commit e91fece. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fetch change request heads from each host's ref namespace in
GitManagerchangeRequestHeadReftoSourceControlProviderto return the GitHub pull-request or GitLab merge-request head ref, ornullfor Bitbucket and Azure DevOpsmaterializePullRequestHeadBranchin GitManager.ts to fetch the provider-selected head ref instead of always deriving a GitHub ref from the pull-request number; for hosts without a head ref, same-repository requests fall back to the primary remote branch and cross-repository requests fail with a specificGitManagerErrorfetchPullRequestBranchandfetchPullRequestHeadCommitin GitVcsDriverCore.ts to accept an explicit head-ref input rather than a pull-request numberrefs/pull/<n>/headfetch; reused worktrees on those hosts refresh from the checked-out branch's upstream only when that upstream names the requested head branchMacroscope summarized e91fece.